﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Widgetsphere.Core.Util;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;
using System.Configuration;

namespace Widgetsphere.Core.Util
{
	public static class LINQDynamicCompile
	{
		private static Dictionary<string, MethodInfo> queryDictionary = new Dictionary<string, MethodInfo>();

		public static MethodInfo GetLINQMethodPaged(string linq, string businessNamespace, string objectName)
		{
			string hash = HashHelper.ComputeHash(linq + "|" + businessNamespace + "|" + objectName + "|Paged");

			MethodInfo method = null;
			if (!queryDictionary.ContainsKey(hash))
			{
				method = LINQDynamicCompile.CreateScriptClassPaged(Guid.NewGuid(), businessNamespace, linq, objectName);
				queryDictionary.Add(hash, method);
			}
			else
				method = queryDictionary[hash];
			return method;

		}

		public static MethodInfo GetLINQMethodAll(string linq, string businessNamespace, string objectName)
		{
			string hash = HashHelper.ComputeHash(linq + "|" + businessNamespace + "|" + objectName + "|All");

			MethodInfo method = null;
			if (!queryDictionary.ContainsKey(hash))
			{
				method = LINQDynamicCompile.CreateScriptClassAll(Guid.NewGuid(), businessNamespace, linq, objectName);
				queryDictionary.Add(hash, method);
			}
			else
				method = queryDictionary[hash];
			return method;

		}

		private static MethodInfo CreateScriptClassPaged(System.Guid currentGuid, string businessNamespace, string linq, string objectName)
		{
			string guid = currentGuid.ToString("N");
			StringBuilder sb = new StringBuilder();
			sb.AppendLine("using System;");
			sb.AppendLine("using System.Linq;");
			sb.AppendLine("using System.Linq.Expressions;");
			sb.AppendLine("using " + businessNamespace + ".Objects;");
			sb.AppendLine("using " + businessNamespace + ".LINQ;");
			sb.AppendLine("namespace Widgetpshere.MemoryGeneration");
			sb.AppendLine("{");
			sb.AppendLine("public static class " + objectName + "Queries" + guid);
			sb.AppendLine("{");
			sb.AppendLine("    public static " + objectName + "Collection ZZ" + guid + "(" + objectName + "Paging paging)");
			sb.AppendLine("    {");
			sb.AppendLine("        return " + objectName + "Collection.RunSelect(" + linq + ", paging);");
			sb.AppendLine("    }");
			sb.AppendLine("}");
			sb.AppendLine("}");

			try
			{
				var codeDomProvider = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
				CompilerParameters cp = new CompilerParameters();


				string appServerBinDirectory = ConfigurationManager.AppSettings["AppServerBinDirectory"];
				if (string.IsNullOrEmpty(appServerBinDirectory))
				{
					Uri uri = new Uri(Assembly.GetExecutingAssembly().CodeBase);
					FileInfo fi = new FileInfo(uri.LocalPath);
					appServerBinDirectory = fi.Directory.FullName;
				}

				if (!string.IsNullOrEmpty(appServerBinDirectory))
				{
					cp.CompilerOptions = string.Format(@"/lib:""{0}""", appServerBinDirectory);
				}
				cp.ReferencedAssemblies.Add("System.dll");
				cp.ReferencedAssemblies.Add("System.Core.dll");
				cp.ReferencedAssemblies.Add(businessNamespace.Remove(businessNamespace.Length - 9) + ".dll");
				cp.ReferencedAssemblies.Add("Widgetsphere.Core.dll");
				cp.GenerateExecutable = false;
				cp.GenerateInMemory = true;
				CompilerResults cr = codeDomProvider.CompileAssemblyFromSource(cp, sb.ToString());
				if (cr.Errors.HasErrors)
				{
					StringBuilder error = new StringBuilder();
					error.Append("Error Compiling Expression: ");
					foreach (CompilerError err in cr.Errors)
					{
						error.AppendFormat("{0}\n", err.ErrorText);
					}
					throw new Exception("Error Compiling Expression: " + error.ToString());
				}
				Assembly assembly = cr.CompiledAssembly;
				Type[] types = assembly.GetTypes();
				if (types.Length == 1)
					return types[0].GetMethod("ZZ" + guid);
			}
			catch (Exception ex)
			{
				throw ex;
			}
			return null;
		}

		private static MethodInfo CreateScriptClassAll(System.Guid currentGuid, string businessNamespace, string linq, string objectName)
		{
			string guid = currentGuid.ToString("N");
			StringBuilder sb = new StringBuilder();
			sb.AppendLine("using System;");
			sb.AppendLine("using System.Linq;");
			sb.AppendLine("using System.Linq.Expressions;");
			sb.AppendLine("using " + businessNamespace + ".Objects;");
			sb.AppendLine("using " + businessNamespace + ".LINQ;");
			sb.AppendLine("namespace Widgetpshere.MemoryGeneration");
			sb.AppendLine("{");
			sb.AppendLine("public static class " + objectName + "Queries" + guid);
			sb.AppendLine("{");
			sb.AppendLine("    public static " + objectName + "Collection ZZ" + guid + "()");
			sb.AppendLine("    {");
			sb.AppendLine("        return " + objectName + "Collection.RunSelect(" + linq + ");");
			sb.AppendLine("    }");
			sb.AppendLine("}");
			sb.AppendLine("}");

			try
			{
				CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider("C#");
				CompilerParameters cp = new CompilerParameters();
				//TODO: Set Uri Through Different Variable
				Uri uri = new Uri(Assembly.GetExecutingAssembly().CodeBase);
				FileInfo fi = new FileInfo(uri.AbsolutePath);
				string appServerBinDirectory = fi.DirectoryName;
				appServerBinDirectory = appServerBinDirectory.Replace("%20", " ");
				if (!string.IsNullOrEmpty(appServerBinDirectory))
				{
					cp.CompilerOptions = string.Format(@"/lib:""{0}""", appServerBinDirectory);
				}
				cp.ReferencedAssemblies.Add("System.dll");
				cp.ReferencedAssemblies.Add("System.Core.dll");
				cp.ReferencedAssemblies.Add(businessNamespace.Remove(businessNamespace.Length - 9) + ".dll");
				cp.ReferencedAssemblies.Add("Widgetsphere.Core.dll");
				cp.GenerateExecutable = false;
				cp.GenerateInMemory = true;
				CompilerResults cr = codeDomProvider.CompileAssemblyFromSource(cp, sb.ToString());
				if (cr.Errors.HasErrors)
				{
					StringBuilder error = new StringBuilder();
					error.Append("Error Compiling Expression: ");
					foreach (CompilerError err in cr.Errors)
					{
						error.AppendFormat("{0}\n", err.ErrorText);
					}
					throw new Exception("Error Compiling Expression: " + error.ToString());
				}
				Assembly assembly = cr.CompiledAssembly;
				Type[] types = assembly.GetTypes();
				if (types.Length == 1)
					return types[0].GetMethod("ZZ" + guid);
			}
			catch (Exception ex)
			{
				throw ex;
			}
			return null;
		}

	}

}